<?php

/**
 * Depends on PEAR/XML_Serializer.
 */
require_once "XML/Unserializer.php";

/**
 * Uses PEAR_Exception to handle errors.
 */
require_once "PEAR/Exception.php";

/**
 * Array to XML conversion function.
 */
require_once "Plesk/ArrayToXml.php";

/**
 * This class is responsible for making all requests to the Plesk Agent.
 *
 * It uses the SimpleXml library to parse the request and cURL to send it.
 *
 * @author Pedro Padron <ppadron@w3p.com.br>
 * @package Services_Plesk
 * @version 0.2.2-beta
 */
class Services_Plesk {

    /**
     * Plesk server
     */
    private $host             = null;

    /**
     * User login. Can be admin or a Plesk client with permissions to use the API.
     */
    private $login            = null;

    /**
     * User password
     *
     * @var string
     */
    private $passwd           = null;

    /**
     * Plesk API protocol version. Default is 1.4.2.0.
     *
     * @var string
     */
    private $protocolVersion  = "1.4.2.0";

    /**
     * Stores all element/operation information about the request.
     *
     * @var string
     */
    private $arrayRequestInfo   = null;

    /**
     * The cURL resource used to make the requests
     *
     * @var resource
     */
    private $curlResource     = null;

    /**
     * Default tag name used by XML_Serializer.
     *
     * If you want to have multiple tags with the same name in your request,
     * you should set this to the name of that tag. For example, if you want to
     * fetch information about several domains, you would do something like this:
     *
     * $request["domain"]["get"]["filter"][] = "domain1.com";
     * $request["domain"]["get"]["filter"][] = "domain2.com";
     * $request["domain"]["get"]["filter"][] = "domain3.com";
     *
     * Since you are creating an array under the "filter" element without specifying
     * an index, those elements wouldn't have a tag name, that's why you can set
     * a default tag name to be used in your request.
     *
     */
    private $defaultTagName   = null;

    /**
     * Class constructor
     *
     * @param string $host
     * @param string $login
     * @param string $passwd
     * @param string $protocolVersion
     */
    function __construct($host, $login, $passwd, $protocolVersion = "1.4.2.0") {
        $this->host            = $host;
        $this->login           = $login;
        $this->passwd          = $passwd;
        $this->protocolVersion = $protocolVersion;
    }

    /**
     * Converts an associative array into a XML string using XML_Serializer
     *
     * @param Array $arrayRequest
     * @return String
     */
    private function _arrayToXml($arrayRequest) {

        $stringXml  = "<packet version=\"" . $this->protocolVersion . "\">";
        $stringXml .= Services_Plesk_array_to_xml($arrayRequest);
        $stringXml .= "</packet>";

        return $stringXml;

    }

    /**
     * Checks if there were any request or response errors.
     *
     * @param String Result obtained by the request via cURL
     * @throws PEAR_Exception
     * @return void
     *
     */
    private function _checkForErrors($arrayResponse) {

        /* Usually this will get parse errors */
        if (isset($arrayResponse["system"]["errtext"])) {
            throw new PEAR_Exception($arrayResponse["system"]["errtext"]);
        }


        foreach ($this->arrayRequestInfo as $element => $valueOperation) {

            /* If more than one operation is being executed */
            if (is_array($valueOperation)) {

                foreach ($valueOperation as $index => $operation) {

                    if ($arrayResponse[$element][$operation][$index]["result"]["status"] == "error") {
                        $errCode = $arrayResponse[$element][$operation][$index]["result"]["errcode"];
                        $errMsg  = $arrayResponse[$element][$operation][$index]["result"]["errtext"];
                        throw new PEAR_Exception($errCode, $errMsg);
                    }

                }

            } else {

                if (isset($arrayResponse[$element][$valueOperation]["result"][0])) {

                    foreach ($arrayResponse[$element][$valueOperation]["result"] as $result) {

                        if ($result["status"] == "error") {
                            $errMsg  = $result["errtext"];
                            $errCode = $result["errcode"];
                            throw new PEAR_Exception($errMsg, $errCode);
                        }

                    }

                }

                if ($arrayResponse[$element][$valueOperation]["result"]["status"] == "error") {
                    $errMsg  = $arrayResponse[$element][$valueOperation]["result"]["errtext"];
                    $errCode = $arrayResponse[$element][$valueOperation]["result"]["errcode"];
                    throw new PEAR_Exception($errMsg, $errCode);
                }

            }
        }



    }

    /**
     * Creates and configures the cURL resource.
     */
    private function _createResource() {

        /* Initializes the Curl engine */
        $this->curlResource = curl_init();

        /* Do not verify host SSL authenticity */
        curl_setopt($this->curlResource, CURLOPT_SSL_VERIFYHOST, 0);

        /* Do not check certificate's authenticity */
        curl_setopt($this->curlResource, CURLOPT_SSL_VERIFYPEER, false);

        /* Sets POST as sending method */
        curl_setopt($this->curlResource, CURLOPT_POST, true);

        /* Demands that the data is returned instead of sent to the browser */
        curl_setopt($this->curlResource, CURLOPT_RETURNTRANSFER, true);

        /* Sets the URL */
        curl_setopt($this->curlResource,
                    CURLOPT_URL,
                    "https://" . $this->host . ":8443/enterprise/control/agent.php");

        /* Configure the request header */
        curl_setopt($this->curlResource,
                    CURLOPT_HTTPHEADER,
                    array("HTTP_AUTH_LOGIN:  " . $this->login,
                          "HTTP_AUTH_PASSWD: " . $this->passwd,
                          "HTTP_PRETTY_PRINT: true",
                          "Content-Type: text/xml"));

    }

    /**
     * Stores some information about the request (element and operation).
     *
     * To correctly retrieve the results, the object must know which elements
     * are being queried and which operations are being executed. It will
     * work even if you have multiple queries in the same request, like a
     * domain/del and client/get, where 'domain' and 'client' are the elements
     * and 'del' and 'get' are the operations.
     *
     * @param string $arrayRequest
     */
    private function _setRequestInfo($arrayRequest) {

        foreach ($arrayRequest as $keyElement => $valueElement) {

            foreach ($valueElement as $keyOperation => $valueOperation) {

                if (isset($valueOperation[0])) {

                    foreach ($valueOperation as $index => $operation) {

                        $this->arrayRequestInfo[$keyElement][$index] = $keyOperation;

                    }

                } else {

                    $this->arrayRequestInfo[$keyElement] = $keyOperation;
                }


            }

        }
    }

    /**
     * Converts an XML String into an associative array using XML_Unserializer
     *
     * @param String $stringXml
     * @throws PEAR_Exception
     * @return Array
     */
    private function _xmlToArray($stringXml) {

        $options = array("complexType"  => "array",
                         "returnResult" => true);

        $objUnserializer = new XML_Unserializer($options);
        $arrayResult     = $objUnserializer->unserialize($stringXml);

        if (PEAR::isError($arrayResult)) {
            throw new PEAR_Exception($arrayResult->getMessage());
        }

        return $arrayResult;

    }

    /**
     * Uses the information stored on $arrayRequestInfo to retrieve the result.
     *
     * @param Array
     * @return Array
     */
    private function _parseResultArray($arrayResponse) {

        foreach ($this->arrayRequestInfo as $element => $valueOperation) {

            if (is_array($valueOperation)) {

                foreach ($valueOperation as $index => $operation) {
                    $arrayResult[$element][$operation][$index] = $arrayResponse[$element][$operation][$index]["result"];
                }

            } else {
                $arrayResult[$element][$valueOperation] = $arrayResponse[$element][$valueOperation];
            }

        }

        return $arrayResult;

    }


    /**
     * Sends the request to the Plesk's API
     *
     * @var string
     * @return string | null
     * @throws PEAR_Exception
     */
    public function execXml($xmlString) {

        if (empty($xmlString)) {
            return null;
        }

        $this->_createResource();

        /* Sets into the cURL resource the request string */
        curl_setopt($this->curlResource, CURLOPT_POSTFIELDS, $xmlString);

        /* Executes the request */
        $stringResult = curl_exec($this->curlResource);

        /* Look for errors */
        if (curl_errno($this->curlResource)) {
            $errorMsg  = curl_error($this->curlResource);
            $errorCode = curl_errno($this->curlResource);
            curl_close($this->curlResource);
            throw new PEAR_Exception($errorMsg, $errorCode);
        }

        curl_close($this->curlResource);

        $this->curlResource = null;

        return $stringResult;

    }

    /**
     * Make the request to the Plesk server.
     *
     * @param Array $arrayRequest
     * @return Array
     * @throws PEAR_Exception
     */
    function exec($arrayRequest) {

        /* Do we have CURL? */
        if (!function_exists('curl_init')) {
            throw new PEAR_Exception('CURL Extension not detected');
        }

        $this->_setRequestInfo($arrayRequest);

        $xml         = $this->_arrayToXml($arrayRequest);

        $xmlResult   = $this->execXml($xml);

        $arrayResult = $this->_xmlToArray($xmlResult);

        /* Checks for any errors - will throw a PEAR_Exception if it finds */
        $this->_checkForErrors($arrayResult);
        $arrayResult = $this->_parseResultArray($arrayResult);

        return $arrayResult;

    }

}

?>